﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;


namespace advadev.CanOpen
{

    /// <summary>
    /// Represents a layer setting services switch state selective transfer
    /// </summary>
    public class LayerSettingServicesSwitchStateSelectiveTransfer : ITransfer
    {

        /// <summary>for internal use</summary>
        private bool _isMasterTransfer;
        /// <summary>for internal use</summary>
        private bool _isTransferCompleteAfterSending;
        /// <summary>for internal use</summary>
        private CanOpenMessage _receivedCanOpenMessage;
        /// <summary>for internal use</summary>
        private LayerSettingServicesSwitchStateSelectiveMessageReceivedEventHandler _layerSettingServicesSwitchStateSelectiveMessageReceivedEventHandler;
        /// <summary>for internal use</summary>
        private Collection<CanOpenMessage> _pendingSendMessages = new Collection<CanOpenMessage>();
        /// <summary>for internal use</summary>
        private ReadOnlyCollection<CanOpenMessage> _pendingSendMessagesReference;
        /// <summary>
        /// Default send COB-ID
        /// </summary>
        static public readonly UInt16 DefaultMasterCobId = 0x7E5;
        /// <summary>
        /// Default receive COB-ID
        /// </summary>
        static public readonly UInt16 DefaultSlaveCobId = 0x7E4;


        /// <summary>
        /// Initializes a new instance of the
        /// LayerSettingServicesSwitchStateSelectiveTransfer class by the given
        /// parameter
        /// </summary>
        /// <param name="receivedCanOpenMessage">
        /// Received can open message
        /// </param>
        /// <param name="masterCobId">
        /// Master COB-ID
        /// </param>
        /// <param name="slaveCobId">
        /// Slave COB-ID
        /// </param>
        /// <param name="layerSettingServicesSwitchStateSelectiveMessageReceivedEventHandler">
        /// Layer setting services switch state selective message received event
        /// handler (or null)
        /// </param>
        private LayerSettingServicesSwitchStateSelectiveTransfer(CanOpenMessage receivedCanOpenMessage, UInt16 masterCobId, UInt16 slaveCobId, LayerSettingServicesSwitchStateSelectiveMessageReceivedEventHandler layerSettingServicesSwitchStateSelectiveMessageReceivedEventHandler)
        {
            _isMasterTransfer = false;
            _receivedCanOpenMessage = receivedCanOpenMessage;
            _layerSettingServicesSwitchStateSelectiveMessageReceivedEventHandler = layerSettingServicesSwitchStateSelectiveMessageReceivedEventHandler;
            MasterCobId = masterCobId;
            SlaveCobId = slaveCobId;
        }

        /// <summary>
        /// Initializes a new instance of the
        /// LayerSettingServicesSwitchStateSelectiveTransfer class by the given
        /// parameter
        /// </summary>
        /// <param name="vendorId">
        /// Vendor id
        /// </param>
        /// <param name="productCode">
        /// Product code
        /// </param>
        /// <param name="revisionNumber">
        /// Revision number
        /// </param>
        /// <param name="serialNumber">
        /// Serial number
        /// </param>
        public LayerSettingServicesSwitchStateSelectiveTransfer(CanOpenUnsigned32 vendorId, CanOpenUnsigned32 productCode, CanOpenUnsigned32 revisionNumber, CanOpenUnsigned32 serialNumber)
            : this(vendorId, productCode, revisionNumber, serialNumber, DefaultMasterCobId, DefaultSlaveCobId)
        {
        }

        /// <summary>
        /// Initializes a new instance of the
        /// LayerSettingServicesSwitchStateSelectiveTransfer class by the given
        /// parameter
        /// </summary>
        /// <param name="vendorId">
        /// Vendor id
        /// </param>
        /// <param name="productCode">
        /// Product code
        /// </param>
        /// <param name="revisionNumber">
        /// Revision number
        /// </param>
        /// <param name="serialNumber">
        /// Serial number
        /// </param>
        /// <param name="masterCobId">
        /// Master COB-ID
        /// </param>
        /// <param name="slaveCobId">
        /// Slave COB-ID
        /// </param>
        public LayerSettingServicesSwitchStateSelectiveTransfer(CanOpenUnsigned32 vendorId, CanOpenUnsigned32 productCode, CanOpenUnsigned32 revisionNumber, CanOpenUnsigned32 serialNumber, UInt16 masterCobId, UInt16 slaveCobId)
        {
            _isMasterTransfer = true;
            VendorId = vendorId;
            ProductCode = productCode;
            RevisionNumber = revisionNumber;
            SerialNumber = serialNumber;
            MasterCobId = masterCobId;
            SlaveCobId = slaveCobId;
        }


        /// <summary>
        /// Gets or sets the vendor id
        /// </summary>
        private CanOpenUnsigned32 VendorId
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the product code
        /// </summary>
        private CanOpenUnsigned32 ProductCode
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the revision number
        /// </summary>
        private CanOpenUnsigned32 RevisionNumber
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the serial number
        /// </summary>
        private CanOpenUnsigned32 SerialNumber
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the master COB-ID
        /// </summary>
        private UInt16 MasterCobId
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the slave COB-ID
        /// </summary>
        private UInt16 SlaveCobId
        {
            get;
            set;
        }


        /// <summary>
        /// Checks if the given received can open message is a layer setting
        /// services switch state selective transfer request and if so it
        /// returns as out parameter a matching layer setting services switch
        /// state global transfer instance
        /// </summary>
        /// <param name="receivedCanOpenMessage">
        /// Received can open message
        /// </param>
        /// <param name="layerSettingServicesSwitchStateSelectiveTransfer">
        /// A matching layer setting services switch state selective transfer
        /// instance
        /// </param>
        /// <param name="layerSettingServicesSwitchStateSelectiveMessageReceivedEventHandler">
        /// Layer setting services switch state selective message received event
        /// handler (or null)
        /// </param>
        /// <returns>
        /// True if the received can open message is a layer setting services
        /// switch state selective transfer request, false otherwise
        /// </returns>
        static public bool IsLayerSettingServicesSwitchStateSelectiveTransfer(CanOpenMessage receivedCanOpenMessage, out LayerSettingServicesSwitchStateSelectiveTransfer layerSettingServicesSwitchStateSelectiveTransfer, LayerSettingServicesSwitchStateSelectiveMessageReceivedEventHandler layerSettingServicesSwitchStateSelectiveMessageReceivedEventHandler)
        {
            return IsLayerSettingServicesSwitchStateSelectiveTransfer(receivedCanOpenMessage, out layerSettingServicesSwitchStateSelectiveTransfer, DefaultMasterCobId, DefaultSlaveCobId, layerSettingServicesSwitchStateSelectiveMessageReceivedEventHandler);
        }

        /// <summary>
        /// Checks if the given received can open message is a layer setting
        /// services switch state selective transfer request and if so it
        /// returns as out parameter a matching layer setting services switch
        /// state global transfer instance
        /// </summary>
        /// <param name="receivedCanOpenMessage">
        /// Received can open message
        /// </param>
        /// <param name="layerSettingServicesSwitchStateSelectiveTransfer">
        /// A matching layer setting services switch state selective transfer
        /// instance
        /// </param>
        /// <param name="masterCobId">
        /// Master COB-ID
        /// </param>
        /// <param name="slaveCobId">
        /// Slave COB-ID
        /// </param>
        /// <param name="layerSettingServicesSwitchStateSelectiveMessageReceivedEventHandler">
        /// Layer setting services switch state selective message received event
        /// handler (or null)
        /// </param>
        /// <returns>
        /// True if the received can open message is a layer setting services
        /// switch state selective transfer request, false otherwise
        /// </returns>
        static public bool IsLayerSettingServicesSwitchStateSelectiveTransfer(CanOpenMessage receivedCanOpenMessage, out LayerSettingServicesSwitchStateSelectiveTransfer layerSettingServicesSwitchStateSelectiveTransfer, UInt16 masterCobId, UInt16 slaveCobId, LayerSettingServicesSwitchStateSelectiveMessageReceivedEventHandler layerSettingServicesSwitchStateSelectiveMessageReceivedEventHandler)
        {
            // check parameter
            if (receivedCanOpenMessage == null)
            {
                layerSettingServicesSwitchStateSelectiveTransfer = null;
                return false;
            }

            // check COB-ID
            if (receivedCanOpenMessage.CanId != masterCobId)
            {
                layerSettingServicesSwitchStateSelectiveTransfer = null;
                return false;
            }

            // check command specifier
            if (receivedCanOpenMessage.Byte0 != (byte)0x40 && receivedCanOpenMessage.Byte0 != (byte)0x41 && receivedCanOpenMessage.Byte0 != (byte)0x42 && receivedCanOpenMessage.Byte0 != (byte)0x43)
            {
                layerSettingServicesSwitchStateSelectiveTransfer = null;
                return false;
            }

            // create transfer instance
            layerSettingServicesSwitchStateSelectiveTransfer = new LayerSettingServicesSwitchStateSelectiveTransfer(receivedCanOpenMessage, masterCobId, slaveCobId, layerSettingServicesSwitchStateSelectiveMessageReceivedEventHandler);
            return true;
        }

        /// <summary>
        /// Starts the master transfer
        /// </summary>
        private void startMasterTransfer()
        {
            UInt16 canId = MasterCobId;
            byte byte0 = 0x00;
            byte byte1 = 0x00;
            byte byte2 = 0x00;
            byte byte3 = 0x00;
            byte byte4 = 0x00;
            IList<byte> vendorIdByteArray = (VendorId as ICanOpenValueObject).ToByteArray();
            byte0 = 0x40;
            byte1 = (byte)(vendorIdByteArray.Count > 0 ? vendorIdByteArray[0] : 0);
            byte2 = (byte)(vendorIdByteArray.Count > 1 ? vendorIdByteArray[1] : 0);
            byte3 = (byte)(vendorIdByteArray.Count > 2 ? vendorIdByteArray[2] : 0);
            byte4 = (byte)(vendorIdByteArray.Count > 3 ? vendorIdByteArray[3] : 0);
            _pendingSendMessages.Add(new CanOpenMessage(canId, byte0, byte1, byte2, byte3, byte4, 0x00, 0x00, 0x00, 8));
            IList<byte> productCodeByteArray = (ProductCode as ICanOpenValueObject).ToByteArray();
            byte0 = 0x41;
            byte1 = (byte)(productCodeByteArray.Count > 0 ? productCodeByteArray[0] : 0);
            byte2 = (byte)(productCodeByteArray.Count > 1 ? productCodeByteArray[1] : 0);
            byte3 = (byte)(productCodeByteArray.Count > 2 ? productCodeByteArray[2] : 0);
            byte4 = (byte)(productCodeByteArray.Count > 3 ? productCodeByteArray[3] : 0);
            _pendingSendMessages.Add(new CanOpenMessage(canId, byte0, byte1, byte2, byte3, byte4, 0x00, 0x00, 0x00, 8));
            IList<byte> revisionNumberByteArray = (RevisionNumber as ICanOpenValueObject).ToByteArray();
            byte0 = 0x42;
            byte1 = (byte)(revisionNumberByteArray.Count > 0 ? revisionNumberByteArray[0] : 0);
            byte2 = (byte)(revisionNumberByteArray.Count > 1 ? revisionNumberByteArray[1] : 0);
            byte3 = (byte)(revisionNumberByteArray.Count > 2 ? revisionNumberByteArray[2] : 0);
            byte4 = (byte)(revisionNumberByteArray.Count > 3 ? revisionNumberByteArray[3] : 0);
            _pendingSendMessages.Add(new CanOpenMessage(canId, byte0, byte1, byte2, byte3, byte4, 0x00, 0x00, 0x00, 8));
            IList<byte> serialNumberByteArray = (SerialNumber as ICanOpenValueObject).ToByteArray();
            byte0 = 0x43;
            byte1 = (byte)(serialNumberByteArray.Count > 0 ? serialNumberByteArray[0] : 0);
            byte2 = (byte)(serialNumberByteArray.Count > 1 ? serialNumberByteArray[1] : 0);
            byte3 = (byte)(serialNumberByteArray.Count > 2 ? serialNumberByteArray[2] : 0);
            byte4 = (byte)(serialNumberByteArray.Count > 3 ? serialNumberByteArray[3] : 0);
            _pendingSendMessages.Add(new CanOpenMessage(canId, byte0, byte1, byte2, byte3, byte4, 0x00, 0x00, 0x00, 8));
            TransferComplete = false;
        }

        /// <summary>
        /// Starts the slave transfer
        /// </summary>
        private void startSlaveTransfer()
        {
            // initialization
            List<byte> byteArray = new List<byte>();
            byteArray.Add(_receivedCanOpenMessage.Byte1);
            byteArray.Add(_receivedCanOpenMessage.Byte2);
            byteArray.Add(_receivedCanOpenMessage.Byte3);
            byteArray.Add(_receivedCanOpenMessage.Byte4);

            // process received message
            if (_receivedCanOpenMessage.Byte0 == (byte)0x40)
            {
                VendorId = new CanOpenUnsigned32(0x1018, 0x01, true, false);
                (VendorId as ICanOpenValueObject).FromByteArray(byteArray);
            }
            else if (_receivedCanOpenMessage.Byte0 == (byte)0x41)
            {
                ProductCode = new CanOpenUnsigned32(0x1018, 0x02, true, false);
                (ProductCode as ICanOpenValueObject).FromByteArray(byteArray);
            }
            else if (_receivedCanOpenMessage.Byte0 == (byte)0x42)
            {
                RevisionNumber = new CanOpenUnsigned32(0x1018, 0x03, true, false);
                (RevisionNumber as ICanOpenValueObject).FromByteArray(byteArray);
            }
            else if (_receivedCanOpenMessage.Byte0 == (byte)0x43)
            {
                SerialNumber = new CanOpenUnsigned32(0x1018, 0x04, true, false);
                (SerialNumber as ICanOpenValueObject).FromByteArray(byteArray);
            }
        }

        /// <summary>
        /// Processes the received message for a master transfer
        /// </summary>
        /// <param name="receivedCanOpenMessage">
        /// Received can open message
        /// </param>
        /// <returns>
        /// True, if the given can open message was related to this transfer,
        /// false otherwise
        /// </returns>
        private bool processReceivedMessageForMasterTransfer(CanOpenMessage receivedCanOpenMessage)
        {
            // check cob-id
            if (receivedCanOpenMessage.CanId != SlaveCobId)
            {
                return false;
            }

            // check command specifier
            if (receivedCanOpenMessage.Byte0 != (byte)0x44)
            {
                return false;
            }

            // close transfer
            _pendingSendMessages.Clear();
            TransferComplete = true;
            return true;
        }

        /// <summary>
        /// Processes the received message for a slave transfer
        /// </summary>
        /// <param name="receivedCanOpenMessage">
        /// Received can open message
        /// </param>
        /// <returns>
        /// True, if the given can open message was related to this transfer,
        /// false otherwise
        /// </returns>
        private bool processReceivedMessageForSlaveTransfer(CanOpenMessage receivedCanOpenMessage)
        {
            // check cob-id
            if (receivedCanOpenMessage.CanId != MasterCobId)
            {
                return false;
            }

            // initialization
            List<byte> byteArray = new List<byte>();
            byteArray.Add(receivedCanOpenMessage.Byte1);
            byteArray.Add(receivedCanOpenMessage.Byte2);
            byteArray.Add(receivedCanOpenMessage.Byte3);
            byteArray.Add(receivedCanOpenMessage.Byte4);

            // process received message
            if (receivedCanOpenMessage.Byte0 == (byte)0x40)
            {
                VendorId = new CanOpenUnsigned32(0x1018, 0x01, true, false);
                (VendorId as ICanOpenValueObject).FromByteArray(byteArray);
            }
            else if (receivedCanOpenMessage.Byte0 == (byte)0x41)
            {
                ProductCode = new CanOpenUnsigned32(0x1018, 0x02, true, false);
                (ProductCode as ICanOpenValueObject).FromByteArray(byteArray);
            }
            else if (receivedCanOpenMessage.Byte0 == (byte)0x42)
            {
                RevisionNumber = new CanOpenUnsigned32(0x1018, 0x03, true, false);
                (RevisionNumber as ICanOpenValueObject).FromByteArray(byteArray);
            }
            else if (receivedCanOpenMessage.Byte0 == (byte)0x43)
            {
                SerialNumber = new CanOpenUnsigned32(0x1018, 0x04, true, false);
                (SerialNumber as ICanOpenValueObject).FromByteArray(byteArray);
            }

            // check transfer state
            if (VendorId != null && ProductCode != null && RevisionNumber != null && SerialNumber != null)
            {
                if (_layerSettingServicesSwitchStateSelectiveMessageReceivedEventHandler != null)
                {
                    _layerSettingServicesSwitchStateSelectiveMessageReceivedEventHandler(this, new LayerSettingServicesSwitchStateSelectiveMessageReceivedEventArgs((uint)VendorId.Value, (uint)ProductCode.Value, (uint)RevisionNumber.Value, (uint)SerialNumber.Value));
                }
                _pendingSendMessages.Clear();
                _pendingSendMessages.Add(new CanOpenMessage(SlaveCobId, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 8));
                _isTransferCompleteAfterSending = true;
            }
            return true;
        }


        #region ITransfer implementation
        /// <summary>
        /// Gets a flag indicating whether the transfer is complete (after
        /// sending the pending send messages)
        /// </summary>
        public bool TransferComplete
        {
            get;
            private set;
        }

        /// <summary>
        /// Gets the abort code (null for successful transfers)
        /// </summary>
        public CanOpenAbortCode AbortCode
        {
            get;
            private set;
        }

        /// <summary>
        /// Gets the pending send messages
        /// </summary>
        public ReadOnlyCollection<CanOpenMessage> PendingSendMessages
        {
            get
            {
                if (_pendingSendMessagesReference == null)
                {
                    _pendingSendMessagesReference = new ReadOnlyCollection<CanOpenMessage>(_pendingSendMessages);
                }
                return _pendingSendMessagesReference;
            }
        }


        /// <summary>
        /// Starts the transfer
        /// </summary>
        public void Start()
        {
            if (_isMasterTransfer)
            {
                startMasterTransfer();
            }
            else
            {
                startSlaveTransfer();
            }
        }

        /// <summary>
        /// Times out the transfer
        /// </summary>
        public void TimeOut()
        {
            AbortCode = new CanOpenAbortCode(0x00, 0x00, 0x04, 0x05);
        }

        /// <summary>
        /// Finalizes the transfer
        /// </summary>
        public void FinalizeTransfer()
        {
        }

        /// <summary>
        /// Continues the transfer after sending messages
        /// </summary>
        public void ContinueAfterSendingMessages()
        {
            _pendingSendMessages.Clear();
            if (_isTransferCompleteAfterSending)
            {
                TransferComplete = true;
            }
        }

        /// <summary>
        /// Processes the received message
        /// </summary>
        /// <param name="receivedCanOpenMessage">
        /// Received can open message
        /// </param>
        /// <returns>
        /// True, if the given can open message was related to this transfer,
        /// false otherwise
        /// </returns>
        public bool ProcessReceivedMessage(CanOpenMessage receivedCanOpenMessage)
        {
            if (_isMasterTransfer)
            {
                return processReceivedMessageForMasterTransfer(receivedCanOpenMessage);
            }
            else
            {
                return processReceivedMessageForSlaveTransfer(receivedCanOpenMessage);
            }
        }
        #endregion ITransfer implementation

    }

}
